/**
 * @ Author: @紫衣小生
 * @ Email: 15227974559@163.com
 * @ Create Time: 2022-12-27 14:16:16
 * @ Modified by: --
 * @ Modified time: 2023-01-04 18:37:46
 * @ Keywords:
 * @ Description: 裁剪工具事务总线
 *
 * 主线流程：
 *  - 根据 config 初始化 dom 结构
 *  - 绑定 dom 的事件监听
 *  - 事件冒泡
 *
 * 关键流程
 *  1- 选择图片：点击按钮 -> 触发input.click -> 选择图片 -> 初始化增量值并绘制图片 -> 预览自动更新
 *  2- 操作图片裁剪：缩放/拖拽 -> 计算当前视图下图片参数 -> 绘制图片 -> 同步预览
 *  3- 重置画布：获取配置项数据 -> 重置蒙版层 -> 重置预览层
 *  4- 辅助功能
 *    a- 保存到本地
 *    b- 上传到服务器
 *
 * 依赖
 *  - figure.class 图形类，将图形的绘制单独封装成其共有方法，可以直接使用 Figure[type] 的方式调用
 *  - log.class 日志类，简单处理一下控制台打印，将有效打印统一化，和临时打印作区分，实现流程回溯
 *
 * 局限性：
 *  - 特殊图形不好使
 *  - 文字不好使
 *
 * 升级方向(重构解决问题)：
 *  - 支持用户上传图形
 *  - 支持文字
 *  - 在现有的基础上支持边框高亮也是一种思路
 * 
 * 升级方案：
 *  - 根本上改变现有的方案，使用获取像素点的方式，进行裁剪，可能就不需要预览框了
 *
 * 关于响应式：本功能并没有使用类似 vue 的响应式来设计配置项的响应式代理，因为配置项比较简单，而其他交互事件使用回调的方式更加合理
 * 关于webpack：当前版本还没有引入 webpack ，接下来会使用 webpack 进行结构重组，使总线更清晰合理
 */

import Log from './log.class'
import Figure from './figure.class'

/**
 * CutImageAuto
 * 事务总线
 * 整合其他工具类、初始化Dom、绑定事件
 */
class CutImageAuto {
  /**
   * 备注一下 config 的字段
   * @param {*} config
   * {
   *  wrap: 容器 ID
   *  cutConfig：蒙版层数据
   *
   * }
   */
  constructor(config) {
    // 最外层容器
    this.$wrap = null
    this.$centerCanvasWrap = null
    this.$formWrap = null

    // 三个 canvas 操作器
    this.maskCv = null
    this.maskCtx = null
    this.maskFigure = null

    this.imageCv = null
    this.imageCtx = null
    this.imageFigure = null

    this.showCv = null
    this.showCtx = null
    this.showFigure = null

    // class 名映射
    let baseClass = 'cut-'
    this.classMap = {
      leftWrap: baseClass + 'left-wrap',
      leftCanvasWrap: baseClass + 'left-canvas-wrap',
      toolsBar: baseClass + 'tools',
      button: baseClass + 'button',
      title: baseClass + 'title',
      label: baseClass + 'label',
      formItem: baseClass + 'form-item',
      formWrap: baseClass + 'form-wrap',
    }

    // 当前绘制图片
    this.image = null
    this.showImage = null

    // 缩放量
    this.ZOOM = 1

    // 图片真实大小
    this.realSize = {
      width: 0,
      height: 0,
    }

    // 视图图片数据
    this.viewSize = {
      x: 0,
      y: 0,
      w: 0,
      h: 0,
    }

    // 图片偏移量
    this.offsetSize = {
      x: 0,
      y: 0,
    }

    // 拖拽之前的备份数据
    this.copyLastViewSize = {}

    // 裁剪区域参数
    this.maskConfig = {
      type: 'code',
      scale: '2.6:1',
      point: 5,
      code: '年',
    }
    // 裁剪图形对应参数
    this.maskArgs = {}

    // 裁剪类型下拉菜单
    this.maskTypeList = [
      { label: '矩形', value: 'rect' },
      { label: '圆形', value: 'circle' },
      { label: '星形', value: 'star' },
      { label: '心形', value: 'love' },
      { label: '多边形', value: 'polygon' },
      { label: '文字', value: 'code' },
    ]

    this.mergeConfig(config)
    this.init()
  }

  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // --[ 初始化逻辑 ]------------------------------------|
  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  // 初始化-合并参数
  mergeConfig(config) {
    if (!config.id) {
      Log._e('缺少核心字段 id')
      return false
    }

    const baseConfig = {
      wheelStep: 0.05, // 鼠标滚轮，步进值
      maskColor: 'rgba(1,1,1,.4)', // 蒙版层颜色
    }

    // 合并 config
    this.config = Object.assign({}, baseConfig, config)

    // 声明 $wrap dom节点
    this.$wrap = document.getElementById(this.config.id)

    Log._i(1, this.config, 'this.config')
  }

  // 初始化-流程总线
  init() {
    // 初始化页面结构
    this.initElement()

    // 初始化蒙版
    this.initMaskView()

    // 绘制回显canvas
    this.setImageCanvasByMask()

    this.drawBackImage()
  }

  /**
   * 初始化-总线-页面结构
   * 分为四块：
   *  - 左边：裁剪工作台+选择图片按钮
   *  - 中间：回显区域+保存本地按钮
   *  - 右边：表单+重置画布按钮
   *  - body：隐藏性质的元素：input:file
   * 为了方便管理 canvas 单独将 canvas 的生成放在了一个方法内部
   * 后续可以的话，可以封装一个工具(jQuery)，替换 document.createElement 事件进行dom的创建
   */
  initElement() {
    const $wrapWidth = this.$wrap.clientWidth
    Log._i(1, $wrapWidth, '$wrapWidth')

    if ($wrapWidth < 960) {
      Log._w('建议容器宽度在 960 以上，以获得更好的体验！')
    }

    this.initCanvas()

    // 0.45 和 0.35 是多次调试的结果，及三块的宽度分别是 45% 35% 20%
    this.appendLeft($wrapWidth * 0.45)
    this.appendCenter($wrapWidth * 0.35)
    this.appendRight()
    this.appendFileInput()
  }

  /**
   * 初始化 Canvas 方便管理
   * 优化 initElement 方法结构
   */
  initCanvas() {
    const maskCanvas = document.createElement('canvas')
    const showCanvas = document.createElement('canvas')
    const imageCanvas = document.createElement('canvas')

    this.maskCv = maskCanvas
    this.maskCtx = maskCanvas.getContext('2d')
    this.maskFigure = new Figure(this.maskCv, this.maskCtx)

    this.imageCv = imageCanvas
    this.imageCtx = imageCanvas.getContext('2d', { willReadFrequently: true })
    this.imageFigure = new Figure(this.imageCv, this.imageCtx)

    this.showCv = showCanvas
    this.showCtx = showCanvas.getContext('2d')
    this.showFigure = new Figure(this.showCv, this.showCtx)
  }

  /**
   * 初始化-支线-左边
   * @param {number} width 宽度
   */
  appendLeft(width) {
    const wrap = document.createElement('div')
    const canvasWrap = document.createElement('div')
    const tools = document.createElement('div')
    const btn = document.createElement('button')

    wrap.className = this.classMap.leftWrap
    canvasWrap.className = this.classMap.leftCanvasWrap
    tools.className = this.classMap.toolsBar
    btn.className = this.classMap.button

    this.imageCv.height = this.maskCv.height = width * 0.75
    this.imageCv.width = this.maskCv.width = width

    this.imageCv.style.zIndex = 1
    this.maskCv.style.zIndex = 2

    canvasWrap.append(this.imageCv)
    canvasWrap.append(this.maskCv)

    btn.innerText = '选择图片'
    tools.appendChild(btn)

    canvasWrap.style.height = parseFloat(width * 0.75) + 'px'
    wrap.appendChild(canvasWrap)
    wrap.appendChild(tools)

    wrap.style.width = width + 'px'
    this.$wrap.appendChild(wrap)

    btn.addEventListener('click', () => {
      this.$fileInput.click()
    })

    // 绑定拖拽
    this.bindDrag(wrap)
    // 绑定缩放
    this.bindZoom(wrap)
  }

  /**
   * 初始化-支线-中间
   * @param {number} width 宽度
   */
  appendCenter(width) {
    const wrap = document.createElement('div')
    const title = document.createElement('div')
    const canvasWrap = document.createElement('div')
    const tools = document.createElement('div')
    const btn = document.createElement('button')
    const uploadBtn = document.createElement('button')

    this.$centerCanvasWrap = canvasWrap

    title.innerText = '预览'
    title.className = this.classMap.title

    canvasWrap.style.position = 'relative'
    canvasWrap.style.height = this.maskCv.height - 50 + 'px'

    tools.className = this.classMap.toolsBar

    btn.className = this.classMap.button
    btn.innerText = '保存本地'
    uploadBtn.className = this.classMap.button
    uploadBtn.innerText = '上传云端'
    tools.appendChild(btn)
    tools.appendChild(uploadBtn)

    canvasWrap.appendChild(this.showCv)

    wrap.appendChild(title)
    wrap.appendChild(canvasWrap)
    wrap.appendChild(tools)

    wrap.style.width = width + 'px'
    this.$wrap.appendChild(wrap)

    // 绑定下载按钮事件
    btn.addEventListener('click', this.saveAsImage.bind(this))

    // 绑定上传按钮事件
    uploadBtn.addEventListener('click', this.uploadImage.bind(this))
  }

  /**
   * 初始化-支线-右边
   * 没有宽度是因为 $wrap 使用了 flex 布局，自动撑开宽度
   */
  appendRight() {
    const wrap = document.createElement('div')
    const title = document.createElement('div')
    const formWrap = document.createElement('div')
    const tools = document.createElement('div')
    const btn = document.createElement('button')
    this.$formWrap = formWrap

    tools.className = this.classMap.toolsBar
    btn.className = this.classMap.button

    title.innerText = '配置工具'
    title.className = this.classMap.title

    btn.innerText = '重置画布'
    tools.appendChild(btn)

    formWrap.style.height = this.maskCv.height - 50 + 'px'
    formWrap.className = this.classMap.formWrap

    this.initFormElement(formWrap)

    wrap.appendChild(title)
    wrap.appendChild(formWrap)
    wrap.appendChild(tools)

    wrap.style.flex = '1'
    this.$wrap.appendChild(wrap)
    btn.addEventListener(
      'click',
      function () {
        this.setConfig(null)
      }.bind(this)
    )
  }

  /**
   * 初始化 - 支线 - 添加上传文件 input
   * 这是一个隐藏的 input
   * 用来被 【选择图片】 按钮触发
   */
  appendFileInput() {
    const ipt = document.createElement('input')
    ipt.type = 'file'
    ipt.style.display = 'none'
    document.body.appendChild(ipt)

    ipt.addEventListener('change', async (e) => {
      const file = e.target.files[0]
      const image = await this.promiseGetImage(file)
      this.image = image
      this.ZOOM = 1
      this.countViewSize('new')
    })

    this.$fileInput = ipt
  }

  /**
   * 初始化 - 支线 - form 表单
   * 实现如下功能：
   *  - 生成下拉菜单，并绑定 change 方法
   *  - 根据下拉菜单值触发配置项的变更，经由【重置画布】按钮触发重绘效果
   */
  initFormElement() {
    this.$formWrap.innerHTML = ''

    this.initMaskItem()
    this.initScaleItem()
    this.initPointNumItem()
  }

  /**
   * 初始化 - 表单项 - 裁剪形状
   */
  initMaskItem() {
    // 裁剪类型
    const maskTypeItem = this.createDom({
      tag: 'div',
      className: this.classMap.formItem,
    })
    const maskTypeLabel = this.createDom({
      tag: 'label',
      className: this.classMap.label,
      text: '裁剪形状：',
    })
    const maskSelect = this.createDom({
      tag: 'select',
    })

    this.maskTypeList.forEach((ele) => {
      const option = document.createElement('option')
      option.value = ele.value
      option.innerText = ele.label
      maskSelect.appendChild(option)
    })

    maskTypeItem.appendChild(maskTypeLabel)
    maskTypeItem.appendChild(maskSelect)
    this.$formWrap.appendChild(maskTypeItem)

    // 下拉框的赋值需要等加载进去以后再赋值
    maskSelect.value = this.maskConfig.type

    maskSelect.addEventListener(
      'change',
      function (e) {
        this.maskConfig.type = e.target.value
        this.reset()
        this.initFormElement(this.$formWrap)
      }.bind(this)
    )
  }

  initScaleItem() {
    if (this.maskConfig.type === 'rect' || this.maskConfig.type === 'star') {
      let scaleItem = this.createDom({
        tag: 'div',
        className: this.classMap.formItem,
      })

      let scalelabel = this.createDom({
        tag: 'div',
        className: this.classMap.label,
        text: this.maskConfig.type === 'rect' ? '长宽比：' : '内外径比：',
      })

      let scaleWidth = this.createDom({
        tag: 'input',
        value: Number(this.maskConfig.scale.split(':')[0]) || 1,
      })

      let scaleHeight = this.createDom({
        tag: 'input',
        value: Number(this.maskConfig.scale.split(':')[1]) || 1,
      })

      let scaleSpan = this.createDom({
        tag: 'span',
        text: ':',
      })

      scaleItem.appendChild(scalelabel)
      scaleItem.appendChild(scaleWidth)
      scaleItem.appendChild(scaleSpan)
      scaleItem.appendChild(scaleHeight)

      this.$formWrap.appendChild(scaleItem)

      let scaleInputHandler = function (e) {
        // 过滤非数字
        e.target.value = e.target.value.replace(/[^0-9.]/gi, '')
        // 重置 maskConfig.scale
        this.maskConfig.scale = scaleWidth.value + ':' + scaleHeight.value
        this.reset()
      }

      scaleWidth.addEventListener('input', scaleInputHandler.bind(this))
      scaleHeight.addEventListener('input', scaleInputHandler.bind(this))
    }
  }

  initPointNumItem() {
    if (this.maskConfig.type === 'star' || this.maskConfig.type === 'polygon') {
      let formItem = this.createDom({
        tag: 'div',
        className: this.classMap.formItem,
      })

      let label = this.createDom({
        tag: 'div',
        className: this.classMap.label,
        text: this.maskConfig.type === 'star' ? '角数：' : '边数：',
      })

      let pointNumInput = this.createDom({
        tag: 'input',
        value: Number(this.maskConfig.point) || 5,
      })

      formItem.appendChild(label)
      formItem.appendChild(pointNumInput)

      this.$formWrap.appendChild(formItem)

      let scaleInputHandler = function (e) {
        // 过滤非数字
        e.target.value = e.target.value.replace(/[^0-9]/gi, '')
        // 重置 maskConfig.scale
        this.maskConfig.point = pointNumInput.value
        this.reset()
      }

      pointNumInput.addEventListener('input', scaleInputHandler.bind(this))
    }
  }

  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // --[ 工具层逻辑 ]------------------------------------|
  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  /**
   * 初始化-支线-左边-工具-绑定拖拽
   * 三个事件完成拖拽功能
   *  - 鼠标按下：指针切换成十字，并且把拖拽状态打开，记录当前初始位置
   *  - 鼠标松开：指针切换成箭头，并且把拖拽状态关闭
   *  - 鼠标移动：计算鼠标偏移量，计算图片最新位置，刷新视图效果
   * 容错处理：
   *  - 鼠标移出 wrap，移除拖拽效果
   *  - 图片移动超出可视范围，拖拽失效
   */
  bindDrag(ele) {
    let isDrag = false
    let mouseStart = { x: 0, y: 0 }

    ele.addEventListener(
      'mousedown',
      function (e) {
        e.preventDefault()
        // 判断鼠标是否在图片内部
        if (
          e.offsetX > this.viewSize.x &&
          e.offsetX < this.viewSize.x + this.viewSize.w &&
          e.offsetY > this.viewSize.y &&
          e.offsetY < this.viewSize.y + this.viewSize.h
        ) {
          isDrag = true
          ele.style.cursor = 'move'
          this.copyLastViewSize = JSON.parse(JSON.stringify(this.viewSize))

          // 记录鼠标起始位置
          mouseStart = {
            x: e.offsetX,
            y: e.offsetY,
          }
        }
      }.bind(this)
    )

    // 绑定鼠标松开事件
    ele.addEventListener(
      'mouseup',
      function (e) {
        e.preventDefault()

        isDrag = false
        ele.style.cursor = 'default'
      }.bind(this)
    )

    // 绑定鼠标移动事件
    ele.addEventListener(
      'mousemove',
      function (e) {
        e.preventDefault()
        if (isDrag) {
          this.offsetSize = {
            x: e.offsetX - mouseStart.x,
            y: e.offsetY - mouseStart.y,
          }
          this.countViewSize('drag')
        }
      }.bind(this)
    )
    // 容错机制，当鼠标移出以后，停止拖拽
    // 解决鼠标移出之后的 mouseup 事件无法被监听到
    ele.addEventListener(
      'mouseout',
      function (e) {
        if (isDrag) {
          e.preventDefault()

          isDrag = false
          ele.style.cursor = 'default'
        }
      }.bind(this)
    )
  }

  // 绑定缩放事件
  bindZoom(ele) {
    ele.addEventListener(
      'mousewheel',
      function (e) {
        e.preventDefault() // 阻止冒泡

        // 深拷贝数据，避免造成数据污染
        this.copyLastViewSize = JSON.parse(JSON.stringify(this.viewSize))

        // 根据滚轮滚动方向半段增减，缩放最小值为 0.3
        if (e.wheelDelta > 0) {
          this.ZOOM += this.config.wheelStep
          this.countViewSize('zoom')
        } else {
          if (this.ZOOM >= 0.3) {
            this.ZOOM -= this.config.wheelStep
            this.countViewSize('zoom')
          }
        }
      }.bind(this)
    )
  }
  /**
   * 生成 dom
   *
   */
  createDom(conf) {
    const { tag, className, text, style, value } = conf
    if (!tag) {
      Log._e('缺少核心参数：tag')
      return false
    }

    let _d = document.createElement(tag)
    className && (_d.className = className)
    text && (_d.innerText = text)
    value && (_d.value = value)

    if (style && typeof style === 'object' && Object.keys(style).length) {
      Object.keys(style).forEach((k) => (_d.style[k] = style[k]))
    }

    return _d
  }
  /**
   * 工具 - 保存图片为 png
   */
  saveAsImage() {
    if (!this.image) {
      Log._e('没有图片，无法下载')
      return false
    }
    const url = this.showCv.toDataURL('image/png')
    const oA = document.createElement('a')
    oA.download = '下载' // 设置下载的文件名，默认是'下载'
    oA.href = url
    document.body.appendChild(oA)
    oA.click()
    oA.remove()
  }

  /**
   * 工具 - 上传图片到服务器
   * 设计思路：
   * [ ] 1- 传入 data/url/type 触发 ajax
   * [V] 2- 传入 callback(file) 将file传入回调函数进行
   */
  uploadImage() {
    console.log('开发中 ···')
  }

  /**
   * 工具- 根据选中的文件返回图片信息
   * @param {File} file 选中的文件
   * @returns
   */
  promiseGetImage(file) {
    return new Promise((resolve, reject) => {
      try {
        const reader = new FileReader()
        reader.readAsDataURL(file)
        reader.onloadend = function (e) {
          // 页面回显选中的图片
          // imageWrap.src = reader.result

          // TODO 对文件类型加强校验
          const image = new Image()
          image.src = reader.result
          image.onload = () => {
            Log._i(1, { width: image.width, height: image.height }, '选中图片')
            return resolve({
              image: reader.result,
              height: image.height,
              width: image.width,
            })
          }
        }
      } catch (error) {
        return reject(error)
      }
    })
  }

  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // --[ 核心逻辑 ]--------------------------------------|
  // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  /**
   * 核心- 计算并绘制蒙版层canvas
   * 策略模式
   * 功能包含：
   *  - 根据 maskConfig 计算裁剪区域的参数 maskArgs
   *  - 绘制 蒙版层 的图形为回字形，裁剪区域透明，非裁剪区域为 config.maskColor
   */
  initMaskView() {
    const center = {
      x: this.maskCv.width / 2,
      y: this.maskCv.height / 2,
    }

    // 裁剪区域边界限制
    let clipZoom = 0.65
    // 临时的参数
    let maskArgs = {}
    // 回调

    // 策略模式计算 maskArgs 并触发绘制方法

    // 矩形流程
    if (this.maskConfig.type === 'rect') {
      // 通过":"切分传入的长宽比，并计算长宽比比例
      let [x, y] = this.maskConfig.scale.split(':')
      let zoom = Number(x) / Number(y)

      // 矩形的四个参数
      let paneW, paneH

      // 通过判断长宽比大小，保证不会超出 canvas 边界
      if (x > y) {
        paneW = this.maskCv.height * clipZoom
        paneH = (this.maskCv.height * clipZoom) / zoom
      } else if (x < y) {
        paneW = this.maskCv.height * clipZoom * zoom
        paneH = this.maskCv.height * clipZoom
      } else {
        paneW = paneH = this.maskCv.height * clipZoom
      }

      maskArgs = {
        x: parseInt((this.maskCv.width - paneW) / 2),
        y: parseInt((this.maskCv.height - paneH) / 2),
        w: parseInt(paneW),
        h: parseInt(paneH),
      }
    }

    // 文字
    if (this.maskConfig.type === 'code') {
      maskArgs = {
        x: center.x,
        y: center.y + 25,
        size: this.maskCv.height * clipZoom,
        code: '飛',
      }
    }

    // 圆形-circle
    if (this.maskConfig.type === 'circle') {
      maskArgs = {
        x: center.x,
        y: center.y,
        r: (this.maskCv.height * clipZoom) / 2,
      }
    }

    // 心形-love
    if (this.maskConfig.type === 'love') {
      maskArgs = {
        x: center.x,
        y: center.y,
        r: (this.maskCv.height * clipZoom) / 2,
      }
    }

    // 星形-star
    if (this.maskConfig.type === 'star') {
      // 通过":"切分传入的长宽比，并计算长宽比比例
      let [x, y] = this.maskConfig.scale.split(':')
      let zoom = x >= y ? y / x : x / y

      maskArgs = {
        x: center.x,
        y: center.y,
        r1: (this.maskCv.height * clipZoom) / 2,
        r2: ((this.maskCv.height * clipZoom) / 2) * zoom,
        n: this.maskConfig.point,
      }
    }

    // 心形-love
    // 多边形-polygon
    if (this.maskConfig.type === 'polygon') {
      maskArgs = {
        x: center.x,
        y: center.y,
        r: (this.maskCv.height * clipZoom) / 2,
        n: this.maskConfig.point,
      }
    }

    this.maskArgs = maskArgs

    // 绘制蒙版层 - 使用回调函数的方式处理
    this.drawMaskLayer()
  }
  /**
   * 核心- 绘制蒙版层 canvas
   * @param {*} cb 回调函数，触发 Figure 的共有方法
   * 由两块组成
   *  - 外面是蒙版阴影部分
   *  - 内层使用绘制图形 结合 globalCompositeOperation 参数实现裁剪
   */
  drawMaskLayer() {
    // 填充满的矩形数据
    const fullMaskArgs = {
      x: 0,
      y: 0,
      width: this.maskCv.width,
      height: this.maskCv.height,
    }

    this.maskFigure.clear()
    // 结合 globalCompositeOperation 画出中间透明的效果
    this.maskFigure.draw(this.maskConfig.type, this.maskArgs)

    this.maskCtx.globalCompositeOperation = 'source-out'

    this.maskCtx.fillStyle = this.config.maskColor
    this.maskCtx.fillRect(
      fullMaskArgs.x,
      fullMaskArgs.y,
      fullMaskArgs.width,
      fullMaskArgs.height
    )

    this.maskCtx.globalCompositeOperation = 'source-over'
  }

  /**
   * 核心- 蒙版区域的图片各项参数 viewConfig
   *
   * 触发条件：
   *  - type = new，设置图片时，初始化增量值
   *  - type = zoom，缩放时计算
   *  - type = drag，拖拽时触发
   * 计算图片视图数据，用来绘制视图
   * 结尾出调用 draw 方法，重绘视图
   * */
  // TODO 本方法需要进行一次拆分，将真实宽度的计算解耦出去，不用每次都计算，而且将 new 进行拆分专门做重新初始化的对应逻辑
  countViewSize(type) {
    if (!this.image) {
      Log._e('没有图片，无法计算图片视图数据！')
      return false
    }

    let viewX, viewY, viewW, viewH, realW, realH

    // 图片宽高
    viewW = this.realSize.width * this.ZOOM
    viewH = this.realSize.height * this.ZOOM

    if (type === 'drag') {
      /* 拖放的数据处理 */
      viewX = this.copyLastViewSize.x + this.offsetSize.x
      viewY = this.copyLastViewSize.y + this.offsetSize.y
    } else if (type === 'zoom') {
      /* 缩放的操作 */

      // 基于左上角缩放
      // viewX = this.copyLastViewSize.x ;
      // viewY = this.copyLastViewSize.y;

      // 基于中心点缩放
      viewX = this.copyLastViewSize.x + (this.copyLastViewSize.w - viewW) / 2
      viewY = this.copyLastViewSize.y + (this.copyLastViewSize.h - viewH) / 2
    } else if (type === 'new') {
      /* 新创建的操作 */
      let zoom = 1

      // 根据宽高判断缩放宽/高
      this.image.height >= this.image.width
        ? (zoom = this.imageCv.height / this.image.height)
        : (zoom = this.imageCv.width / this.image.width)

      // 真实高度
      realW = this.image.width * zoom
      realH = this.image.height * zoom

      // 计算开始位置
      if (this.image.height >= this.image.width) {
        viewX = (this.imageCv.width - realW) / 2
        viewY = 0
      } else {
        viewX = 0
        viewY = (this.imageCv.height - realH) / 2
      }

      // 原始尺寸的理论数据
      this.realSize = {
        x: viewX,
        y: viewY,
        width: realW,
        height: realH,
        centerX: viewX + realW / 2,
        centerY: viewY + realH / 2,
      }
      viewW = realW
      viewH = realH
    }
    // 视图尺寸
    this.viewSize = {
      x: viewX,
      y: viewY,
      w: viewW,
      h: viewH,
      centerX: viewX + this.offsetSize.x + viewW / 2,
      centerY: viewY + this.offsetSize.y + viewH / 2,
    }

    // Log._i(1, this.viewSize, 'this.viewSize')

    // 重绘视图
    this.drawImage()

    // 绘制回显区域
    this.drawBackImage()
  }

  /**
   * 核心- 绘制蒙版区域的图片
   * 根据 viewSize 绘制图层
   */
  drawImage() {
    this.imageFigure.clear()
    this.imageFigure.draw('image', {
      ...this.viewSize,
      image: this.image.image,
    })
  }

  /**
   * 核心- 修改裁剪形状之后，重设预览 canvas 的样式
   * 策略模式
   * 根据不通裁剪形状分情况绘制
   */
  setImageCanvasByMask() {
    this.showCv.style.background = 'rgb(245,245,245)'
    this.showCv.style.position = 'absolute'

    if (this.maskConfig.type === 'rect') {
      this.showCv.width = this.maskArgs.w
      this.showCv.height = this.maskArgs.h
      this.showCv.style.top =
        (this.$centerCanvasWrap.clientHeight - this.showCv.height) / 2 + 'px'
      this.showCv.style.left =
        (this.$centerCanvasWrap.clientWidth - this.showCv.width) / 2 + 'px'
    } else {
      this.showCv.width = this.maskCv.height * 0.65
      this.showCv.height = this.maskCv.height * 0.65
      this.showCv.style.top =
        (this.$centerCanvasWrap.clientHeight - this.showCv.height) / 2 + 'px'
      this.showCv.style.left =
        (this.$centerCanvasWrap.clientWidth - this.showCv.width) / 2 + 'px'
    }
  }

  /**
   * 核心- 绘制回显层
   * 策略模式
   *  - 矩形：满填充
   *  - 其他图形：按照最长边使用 clip() 方法进行裁剪图片
   */
  drawBackImage() {
    this.showFigure.clear()

    let args = null
    if (this.maskConfig.type === 'rect') {
      args = JSON.parse(JSON.stringify(this.maskArgs))
    } else if (
      this.maskConfig.type === 'star' ||
      this.maskConfig.type === 'circle' ||
      this.maskConfig.type === 'polygon' ||
      this.maskConfig.type === 'love'
    ) {
      args = Object.assign(this.maskArgs, {
        x: this.showCv.width / 2,
        y: this.showCv.height / 2,
      })
    } else if (this.maskConfig.type === 'code') {
      args = Object.assign(this.maskArgs, {
        x: this.showCv.width / 2,
        y: this.showCv.height / 2 + 25,
      })
    }

    if (this.maskConfig.type !== 'rect') {
      this.showFigure.draw(this.maskConfig.type, args)
      this.showCtx.clip()
    }

    this.image &&
      this.showFigure.draw('image', {
        x: this.viewSize.x - (this.imageCv.width - this.showCv.width) / 2,
        y: this.viewSize.y - (this.imageCv.height - this.showCv.height) / 2,
        w: this.viewSize.w,
        h: this.viewSize.h,
        image: this.image.image,
      })
  }

  imagdata2Image(data) {
    let canvas = document.createElement('canvas')
    let ctx = canvas.getContext('2d')

    canvas.height = this.showCv.height
    canvas.width = this.showCv.width

    canvas.style.display = 'none'
    ctx.putImageData(data, 0, 0)

    document.body.appendChild(canvas)
    const url = canvas.toDataURL('image/jpeg')

    let image = new Image()
    image.src = url
    this.showImage = image
    document.body.removeChild(canvas)
  }

  /**
   * 核心- 设置参数
   * @param {any} config 设置的数据
   */
  setConfig(config) {
    Log._i(1, config, '重置画布')
    this.config = config ? Object.assign(this.config, config) : this.config

    this.reset()
  }

  /**
   * 核心- 重置
   * 重置时需要做如下工作
   *  - 重绘蒙版
   *  - 重新设置预览的canvas
   *  - 预览canvas重绘
   */
  reset() {
    Log._i(1, 'reset', 'reset')
    // 初始化蒙版
    this.initMaskView()
    // 绘制回显canvas
    this.setImageCanvasByMask()
    // // 绘制预览图片
    this.drawBackImage()
  }
}

const createCut = (config) => new CutImageAuto(config)

export default createCut
